﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.IO;
using System.Collections.Generic;

using System.Linq;

using ScrumTable.Common;
using ScrumTable.Common.Text;
using ScrumTable.DL.Data.Common;
using ScrumTable.DL.Data.Common.Config.Schema;
using ScrumTable.DL.Data.Generic.Properties;
using ScrumTable.DL.Data.Schema.Serialization;

#endregion

namespace ScrumTable.DL.Data.Generic.Config.Schema
{
    using ScrumTable.Common.Logging;

    /// <summary>
    /// Represents a container for SchemaSource instances. This instance
    /// is created during the PlugIn create call.
    /// </summary>
    public class GenericDataSchemaSelector : IDataSchemaSelector
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const string XmlFileExtension = ".xml";
        private const string XmlFileSchemaIndicator = "Schema_";

        private const string InternalSchemaName = "Internal Schema";
        private const string OverwrittenSchemaName = "Overwritten Schema";
        private const string OverwrittenSchemaIndicator = "Override";

        private const string CustomSchemaNamePattern = "{0} - {1}";

        private readonly IDataAssemblyInfo _driverInfo;
        private readonly IList<GenericDataSchemaSource> _dataContainer = new List<GenericDataSchemaSource>();
        private readonly string _internalSchema;
        private int _selectedIndex = -1;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets/sets the selected schema source instance.
        /// </summary>
        public IDataSchemaSource Selected
        {
            get
            {
                if (_selectedIndex == -1)
                {
                    return _dataContainer[_dataContainer.Count - 1];
                }
                return _dataContainer[_selectedIndex];
            }
            set
            {
                PreCondition.AssertNotNull(value, "value");

                int containerIndex = _dataContainer.IndexOf(value as GenericDataSchemaSource);

                if (containerIndex == -1)
                {
                    throw new InvalidOperationException(Resources.ExcDatSchemaSrcInvalidSourceSpecified);
                }
                _selectedIndex = containerIndex;
            }
        }

        /// <summary>
        /// Gets/sets the default schema configuration instance. This corresponds
        /// to the internal configuration instance which cannot be modified.
        /// </summary>
        public IDataSchemaSource Default
        {
            get { return _dataContainer.FirstOrDefault(); }
        }

        private string Version
        {
            get { return _dataContainer[0].SourceData.Version; }
        }

        private string Driver
        {
            get { return _dataContainer[0].SourceData.Driver; }
        }

        private string CurrentDriverDirectory
        {
            get { return Path.GetDirectoryName(GetType().Assembly.Location); }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new SchemaSourceContainer instance using the given argument
        /// as initial schemata instance. This initializes the schema Version and
        /// schema Driver for all instances stored in the current container.
        /// </summary>
        protected GenericDataSchemaSelector(string internalSchema, IDataAssemblyInfo driverInfo)
        {
            PreCondition.AssertNotNullOrEmpty(internalSchema, "internalSchema");
            PreCondition.AssertNotNull(driverInfo, "driverInfo");

            _internalSchema = internalSchema;
            _driverInfo = driverInfo;

            CreateSourceList();
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<IDataSchemaSource> GetEnumerator()
        {
            return (from dataSource in _dataContainer select (IDataSchemaSource)dataSource).GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Creates a new data schema source and returns the created instance.
        /// The new source will contain the default configuration data provided
        /// by the underlying data assembly driver.
        /// </summary>
        public IDataSchemaSource Create()
        {
            throw new NotImplementedException("The Create() operation is currently not supported.");
        }

        private void CreateSourceList()
        {
            // add internal data - this defines the schemata version
            SchemataRoot internalData = new SchemaSerializer().Deserialize(_internalSchema);
            AddSource(new GenericDataSchemaSource(
                    StringUtil.GetLNotNull(internalData.Name, InternalSchemaName),
                    GetLegacyNameList(internalData),
                    internalData));

            FillCustomSchema(internalData.Driver, internalData.Version);
            FillOverrideSchema(internalData.Driver, internalData.Version);
        }

        private void FillOverrideSchema(string driver, string version)
        {
            // add overwrite file path data (for backward compatibility)
            string xmlOverrideFilePath = Path.Combine(CurrentDriverDirectory, GetOverrideSchemaFile(driver));

            // overwrite internal xml data string));
            if (File.Exists(xmlOverrideFilePath))
            {
                FillSchema(xmlOverrideFilePath, OverwrittenSchemaName, driver, version);
            }
        }

        private void FillCustomSchema(string driver, string version)
        {
            // search for more schemata with the appropriate version
            foreach (string filePath in Directory.GetFiles(CurrentDriverDirectory, GetCustomSchemaFiles(driver), SearchOption.AllDirectories))
            {
                if (Path.GetExtension(filePath) == XmlFileExtension)
                {
                    string schemaSourceFileIndicator = Path.GetFileName(filePath).CutOff(XmlFileSchemaIndicator, XmlFileExtension);

                    if (schemaSourceFileIndicator != OverwrittenSchemaIndicator)
                    {
                        FillSchema(
                            filePath,
                            string.Format(
                                CustomSchemaNamePattern,
                                schemaSourceFileIndicator,
                                driver),
                            driver,
                            version);
                    }
                }
            }
        }

        private void FillSchema(string xmlFilePath, string schemaSourceName, string driver, string version)
        {
            SchemataRoot dataToFill = null;

            try
            {
                dataToFill = new SchemaSerializer().Deserialize(File.ReadAllText(xmlFilePath));
            }
            catch (Exception e)
            {
                Logger<GenericDataSchemaSelector>.Warn(string.Format(Resources.ExcDatSchemaSelInvalidXmlFile, xmlFilePath), e);
            }
            
            if (dataToFill != null && dataToFill.Version == version && dataToFill.Driver == driver)
            {
                AddSource(new GenericDataSchemaSource(
                        StringUtil.GetLNotNull(dataToFill.Name, schemaSourceName),
                        GetLegacyNameList(dataToFill), 
                        dataToFill));
            }
        }

        private IList<string> GetLegacyNameList(SchemataRoot schemata)
        {
            return StringUtil.SplitTrim(schemata.LegacyNames, ',');
        }

        private void AddSource(GenericDataSchemaSource schemaToAdd)
        {
            PreCondition.AssertNotNull(schemaToAdd, "schemaToAdd");

            if (_dataContainer.Count != 0)
            {
                if (schemaToAdd.SourceData.Driver != Driver)
                {
                    throw new InvalidOperationException(Resources.ExcDatSchemaSrcInvalidSourceSpecified);
                }
                if (schemaToAdd.SourceData.Version != Version)
                {
                    throw new InvalidOperationException(Resources.ExcDatSchemaSrcInvalidSourceSpecified);
                }
            }

            if (!_dataContainer.Any(source => source.Name == schemaToAdd.Name))
            {
                _dataContainer.Add(schemaToAdd);
            }
        }



        private static string GetOverrideSchemaFile(string driver)
        {
            return string.Format("{0}.{1}{2}{3}",
                driver,
                XmlFileSchemaIndicator,
                OverwrittenSchemaIndicator,
                XmlFileExtension);
        }

        private static string GetCustomSchemaFiles(string driver)
        {
            return string.Format("{0}.{1}*", driver, XmlFileSchemaIndicator);
        }

        #endregion
    }
}